/**
 * @file wa_ll.c
 * 
 */

/*********************
 *      INCLUDES
 *********************/
#include "wa_ll.h"
#include "wa_malloc.h"
/*********************
 *      DEFINES
 *********************/
#define WA_NODE_META_SIZE (sizeof(wa_ll_node*) + sizeof(wa_ll_node*))
#define WA_PREV_P_OFFSET(ll_p) (ll_p->n_size)
#define WA_NEXT_P_OFFSET(ll_p) (ll_p->n_size + sizeof(wa_ll_node*))
/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void node_set_prev(wa_ll * ll_p, wa_ll_node* act, wa_ll_node* prev);
static void node_set_next(wa_ll * ll_p, wa_ll_node* act, wa_ll_node* next);
/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/
/**
 * init a link list
 * @param ll_p point of a link list
 * @param n_size size of node in link list 
 */
void wa_ll_init(wa_ll * ll_p, uint32_t n_size)
{
    ll_p->head = NULL;
    ll_p->tail = NULL;
    ll_p->n_size = n_size;   
}

/**
 * add a node to head of link list
 * @param ll_p point of a link list
 * @return new node's point
 */
void* wa_ll_add_head(wa_ll * ll_p)
{
    wa_ll_node* n_new;

    n_new = (wa_ll_node*)wa_malloc(ll_p->n_size + WA_NODE_META_SIZE);
    
    if(n_new != NULL) 
	{ 
        node_set_prev(ll_p, n_new, NULL);           //new node's perv node is NULL
        node_set_next(ll_p, n_new, ll_p->head);     //new node's next node is former head of link list
        
        if(ll_p->head != NULL) 
		{ 
            //if former head of link list is not NULL, that former head of link list's perv node is new node
            node_set_prev(ll_p, ll_p->head, n_new);
        }
        
        ll_p->head = n_new;         //set the head of link list to new node
        if(ll_p->tail == NULL) 
		{
            //if tail of link list is NULL, that new node is the tail of link list(only one node there)
            ll_p->tail = n_new;
        }
    }
    return (void*)n_new;
}

/**
 * add a node to tail of link list
 * @param ll_p point of a link list
 * @return new node's point
 */
void* wa_ll_add_tail(wa_ll* ll_p)
{   
    wa_ll_node* n_new;

    n_new = (wa_ll_node*)wa_malloc(ll_p->n_size + WA_NODE_META_SIZE);
    
    if(n_new != NULL) 
	{
        node_set_next(ll_p, n_new, NULL); //new node's next node is NULL
        node_set_prev(ll_p, n_new, ll_p->tail); //new node's prev node is former tail of link list
        if(ll_p->tail != NULL) 
		{   
            //if former tail of link list is not NULL, that former tail of link list's next node is new node
            node_set_next(ll_p, ll_p->tail, n_new);
        }
        
        ll_p->tail = n_new;      //tail of link list is new node
        if(ll_p->head == NULL) 
		{ 
            //if head of link list is NULL, new node is the head of link list(there is only one node)
            ll_p->head = n_new;
        }
    }
    return (void*)n_new;
}

/**
 * get the head of link list
 * @param ll_p point of a link list
 * @return head node of link list
 */
void * wa_ll_get_head(wa_ll * ll_p)
{   
    void * head = NULL;
    
    if(ll_p != NULL)    
	{
        head = ll_p->head;
    }
    
    return head;
}

/**
 * get the tail of link list
 * @param ll_p point of a link list
 * @return tail node of link list
 */
void * wa_ll_get_tail(wa_ll * ll_p)
{
    void * tail = NULL;
    
    if(ll_p != NULL)
	{
        tail = ll_p->tail;
    }
    
    return tail;
}

/**
 * get prev node
 * @param ll_p point of a link list
 * @param n_act current node 
 * @return prev node
 */
void * wa_ll_get_prev(wa_ll * ll_p, void * n_act)
{
    void * prev = NULL;
 
    if(ll_p != NULL) 
	{
        wa_ll_node* n_act_d = n_act;
        memcpy(&prev, n_act_d + WA_PREV_P_OFFSET(ll_p), sizeof(void*));
    }
    
    return prev;
}

/**
 * get next node
 * @param ll_p point of a link list
 * @param n_act current node
 * @return next node
 */
void * wa_ll_get_next(wa_ll * ll_p, void * n_act)
{
    void * next = NULL;
 
    if(ll_p != NULL)    
	{
        wa_ll_node* n_act_d = n_act;
        memcpy(&next, n_act_d + WA_NEXT_P_OFFSET(ll_p), sizeof(void*));
    } 
    
    return next;
}

/**
 * remove a node from link list but not free mem
 * @param ll_p point of a link list
 * @param node_p which node want to renove
 */
void wa_ll_remove(wa_ll  * ll_p, void * node_p)
{
    if(wa_ll_get_head(ll_p) == node_p) 
	{
        //if remove head node
        ll_p->head = wa_ll_get_next(ll_p, node_p);
        if(ll_p->head == NULL) 
		{
			//if only one node in link list
            ll_p->tail = NULL;
        }
        else 
		{
            node_set_prev(ll_p, ll_p->head, NULL);
        }   
    }
    else if(wa_ll_get_tail(ll_p) == node_p) 
	{
        //if remove tail node
        ll_p->tail = wa_ll_get_prev(ll_p, node_p);
        if(ll_p->tail == NULL) 
		{
			//if only one node in link list
            ll_p->head = NULL;
        }
        else 
		{
            node_set_next(ll_p, ll_p->tail, NULL);
        }
    }
    else
    {
		//if remove node is not head node or tail node
        wa_ll_node* n_prev = wa_ll_get_prev(ll_p, node_p);
        wa_ll_node* n_next = wa_ll_get_next(ll_p, node_p);
        
        node_set_next(ll_p, n_prev, n_next);
        node_set_prev(ll_p, n_next, n_prev);
    }
}

/**
 * remove all node
 * @param ll_p point of a link list
 */
void wa_ll_clear(wa_ll * ll_p)
{
	void * i;
	void * i_next;

	i = wa_ll_get_head(ll_p);
	i_next = NULL;

	while(i != NULL) 
	{
		i_next = wa_ll_get_next(ll_p, i);

        wa_ll_remove(ll_p, i);
        wa_free(i);

		i = i_next;
	}
}

/**
 * move one node to the head of a new list
 * @param ll_old_p old link list
 * @param ll_new_p new link list
 * @param node which node want to move
 */
void wa_ll_chg_list(wa_ll * ll_old_p, wa_ll * ll_new_p, void * node)
{
    wa_ll_remove(ll_old_p, node);
    
    //set node to head of new link list
    node_set_prev(ll_new_p, node, NULL);      
    node_set_next(ll_new_p, node, ll_new_p->head);
    
    if(ll_new_p->head != NULL) 
	{
        node_set_prev(ll_new_p, ll_new_p->head, node);
    }

    ll_new_p->head = node;        
    if(ll_new_p->tail == NULL) 
	{
        ll_new_p->tail = node;
    }
}


/**********************
 *   STATIC FUNCTIONS
 **********************/

 
/**
 * set prev of a node
 * @param ll_p point of link list
 * @param act current node
 * @param prev which node want to set
 */
static void node_set_prev(wa_ll * ll_p, wa_ll_node* act, wa_ll_node* prev)
{
    if (act == NULL)
    {
        return;
    }
    if (prev != NULL)
    {
        memcpy(act + WA_PREV_P_OFFSET(ll_p), &prev, sizeof(wa_ll_node*));
    }
    else
    {
        memset(act + WA_PREV_P_OFFSET(ll_p), 0, sizeof(wa_ll_node*));
    }
}

/**
 * set next of a node
 * @param ll_p point of link list
 * @param act current node
 * @param next which node want to set
 */
static void node_set_next(wa_ll * ll_p, wa_ll_node* act, wa_ll_node* next)
{
    if (act == NULL)
    {
        return;
    }
    if (next != NULL)
    {
        memcpy(act + WA_NEXT_P_OFFSET(ll_p), &next, sizeof(wa_ll_node*));
    }
    else
    {
        memset(act + WA_NEXT_P_OFFSET(ll_p), 0, sizeof(wa_ll_node*));
    }
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

